﻿//#define TEST_PROGRESS_UPDATE
using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Timers;
using System.IO;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Threading;

namespace LedCenter.Models
{
    public delegate void DataTimeoutHandler(object sender, EventArgs e);
    //public delegate void ProgresChangedHandler(object sender, ProgressChangedEventArgs e);
    
    public class TCPDevice : BaseDevice
    {
        public const int PACKAGE_TIMEOUT = 100000;
        // TCP info
        public string IP{get;set;}// IP
        public int Port{get;set;}

        public TcpClient _tcpClient;
        public IAsyncResult IAR;

        #region | Contructors |
        /// <summary>
        /// Default constructor
        /// </summary>
        /// <returns></returns>
        public TCPDevice():base()
        {
            IP = "0.0.0.0";
            Port = 0;
            DataTimer.Elapsed += new ElapsedEventHandler(DataTimer_Elapsed);
            
            bwSimulator = new BackgroundWorker();
            bwSimulator.DoWork += new DoWorkEventHandler(bwSimulatorDoWork);

            bwSimulator.WorkerReportsProgress = true;
            bwSimulator.WorkerSupportsCancellation = true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="type"></param>
        /// <param name="pass"></param>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public TCPDevice(string name, string imei,string pass, string ip, int port)
            : base(name, imei,DEVICE_TYPE.TCP, pass)
        {
            IP = ip;
            Port = port;
            _tcpClient = null;
            DataTimer.Elapsed += new ElapsedEventHandler(DataTimer_Elapsed);

            bwSimulator = new BackgroundWorker();
            bwSimulator.DoWork += new DoWorkEventHandler(bwSimulatorDoWork);

            bwSimulator.WorkerReportsProgress = true;
            bwSimulator.WorkerSupportsCancellation = true;
        }
        #endregion

        #region | IBaseDevice implementation |
        public override string ToXML()
        {
            return "TCP xml";
        }
        #endregion

        #region | IConnectable implementation |
        /// <summary>
        /// Open connection
        /// </summary>
        /// <returns></returns>
        public override string open()
        {
            base.open();
            string result;
            try
            {
                _tcpClient = new TcpClient(IP,Port);
                //_stream = _tcpClient.GetStream();
                StateObject state = new StateObject();
                //state.workSocket = _tcpClient.Client;
                
                /*
                _tcpClient.Client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                            new AsyncCallback(OnReceive), state);

                */
                if (!_tcpClient.Connected)
                    return "Error";

                // send client version
                /*
                result = SendData(WMAMOBILE_STX, WMA_VERSION, 1, new byte[] { 0x01 }, stream);
                if (!result.Equals(noError))
                {
                    _stream.Close();
                    return "Error";
                }
                 * */
            }
            catch (Exception exception)
            {
                return exception.Message;

            }
            result = "OK";
            return result;
        }

        /// <summary>
        /// Close connection
        /// </summary>
        /// <returns></returns>
        public override void close()
        {

            if (_tcpClient != null && _tcpClient.Connected)
                _tcpClient.Close();
            //if (_stream != null)
            //_stream.Close();
            _tcpClient = null;
        }

        /// <summary>
        /// Write data
        /// </summary>
        /// <param name="data"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public override string writeData(byte[] data, int length)
        {
            string result;
            try{
                //_stream.Write(data,0,length);
            }
            catch(Exception ex)
            {
                result = "error";

                close();
            }
            result = "OK";
            return result;
        }
        // < end of IConnectable 
        #endregion

        #region | Send Data |
        // Send data
        public void sendProgram(ProgramData prog)
        {
            programToSent = prog;
#if SEND_PROGRAM_OLDVERSION
            (new System.Threading.Thread(() =>
            {


                try
                {
                    // fix version 1
#if TEST_OFFLINE
                // start data timeout timer
                    DataTimer.AutoReset = false;
                    DataTimer.Stop();
                    DataTimer.Start();
#else
                    DataTimer.AutoReset = false;
                    DataTimer.Stop();
                    DataTimer.Start();
                    Debug.WriteLine("Sending  update data request...");
                    bDataRequested = false;
                    Sender.vSendUpdateDataRequest(_tcpClient.Client, 1);
                    while (!bDataRequested)
                    {
                        if (bDataRequestedTimeout)
                        {
                            Debug.WriteLine("      > Sending  update data request cancel because of timeout!");
                            return;
                        }
                    }
                    DataTimer.Stop();
                    bDataInfoSent = false;
                    Debug.WriteLine("Sending  update data info...");
                    Sender.vSendUpdateDataInfo(_tcpClient.Client, prog.Info);
                    while (!bDataInfoSent)
                    {
                        if (bDataRequestedTimeout)
                        {
                            Debug.WriteLine("     > Sending  update data info canceled because of timeout!");
                            return;
                        }
                    }
                    Debug.WriteLine("Sent  update data info");

                    for (int i = 0; i < prog.Pakages.Count; i++)
                    {
                        Debug.WriteLine("Sending update data package " + i);
                        bFirmwarePackageSent = false;
                        Sender.vSendUpdateDataPackage(_tcpClient.Client, prog.Pakages[i]);
                        DataTimer.Stop();
                        while (!bFirmwarePackageSent)
                        {
                            if (bFirmwarePackageSentError_checksum)
                            {
                                if (bDataRequestedTimeout) return;
                                Sender.vSendUpdateDataPackage(_tcpClient.Client, prog.Pakages[i]);
                                bFirmwarePackageSentError_checksum = false;
                            }
                        }

#endif
                        //bwFirmwareUpdater.ReportProgress(i * 100 / firmware.Pakages.Count + 1);
                    }


                }
                catch (System.Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                    //MessageBox.Show("Error while sending firmware to selected client\r\n" + ex.Message);
                    return;
                }
            })).Start();
#else
            if (bwSimulator.IsBusy)
                bwSimulator.CancelAsync();
            bwSimulator.RunWorkerAsync();
#endif
        }

        public void sendPrograms(PlaylistData playlist)
        {
            TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            DateTime before = DateTime.Now;
            // Send playlist process
            Task playlistSending = Task.Factory.StartNew(() =>
                {
                    // Send update playlist command to device
                    // reset timeout timer
                    DataTimer.AutoReset = false;
                    DataTimer.Stop();
                    DataTimer.Start();
                    Debug.WriteLine("Sending  update playlist request...");
                    bDataRequested = false;
                    // send data request header
                    Sender.vSendUpdatePlaylistRequest(_tcpClient.Client);
                    while (!bDataRequested)
                    {
                        if (bDataRequestedTimeout)
                        {
                            Debug.WriteLine("      > Sending  update playlist request cancel because of timeout!");
                            return;
                        }
                    }

                    DataTimer.Stop();
                    bDataInfoSent = false;
                    Debug.WriteLine("Sending playlist info...");
                    Sender.vSendPlaylistInfo(_tcpClient.Client, playlist.Info);
                    while (!bDataInfoSent)
                    {
                        if (bDataRequestedTimeout)
                        {
                            Debug.WriteLine("     > Sending  playlist data info canceled because of timeout!");
                            return;
                        }
                    }

                    foreach (ProgramData program in playlist.packages)
                    {
                        
                        sendOneProgramInPlaylist(program);
                    }
                },
                TaskCreationOptions.LongRunning
            );

            // finishes after running
            playlistSending.ContinueWith(task =>
            {
                // detects operation duration
                //TimeSpan duration = DateTime.Now - before;
                //TimeSpan perPixel = new TimeSpan(duration.Ticks);

            }, uiScheduler);
        }

        private void sendOneProgramInPlaylist(ProgramData program)
        {
            int totalSteps = 2 + program.Pakages.Count;
            try
            {
                DataTimer.Stop();
                bDataInfoSent = false;
                Debug.WriteLine("Sending  update data info...");
                Sender.vSendUpdateDataInfo(_tcpClient.Client, program.Info);
                while (!bDataInfoSent)
                {
                    if (bDataRequestedTimeout)
                    {
                        Debug.WriteLine("     > Sending  update data info canceled because of timeout!");
                        return;
                    }
                }
                //bwSimulator.ReportProgress(2 / totalSteps * 100);
                Debug.WriteLine("Sent  update data info");
                for (int i = 0; i < program.Pakages.Count; i++)
                {
                    Debug.WriteLine("Sending update data package " + i);
                    bFirmwarePackageSent = false;
                    Sender.vSendUpdateDataPackage(_tcpClient.Client, program.Pakages[i]);
                    DataTimer.Stop();
                    while (!bFirmwarePackageSent)
                    {
                        if (bFirmwarePackageSentError_checksum)
                        {
                            if (bDataRequestedTimeout) return;
                            Sender.vSendUpdateDataPackage(_tcpClient.Client, program.Pakages[i]);
                            bFirmwarePackageSentError_checksum = false;
                        }
 
                    }
                    //bwSimulator.ReportProgress(i + 3 / totalSteps * 100);
                }


            }
            catch (System.Exception ex)
            {
                Debug.WriteLine(ex.Message);
                //MessageBox.Show("Error while sending firmware to selected client\r\n" + ex.Message);
                return;
            }
        }

        // Update firmware
        public void sendFirmware(BlackboxFirmware firmware)
        {
            //TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            DateTime before = DateTime.Now;
            // Send playlist process
            Task playlistSending = Task.Factory.StartNew(() =>
            {
                // Send update firmware command to device
                // reset timeout timer
                DataTimer.AutoReset = false;
                DataTimer.Stop();
                DataTimer.Start();
                Debug.WriteLine("Sending  update firmware request...");
                bDataRequested = false;
                // send data request header
                Sender.vSendUpdateFirmwareRequest(_tcpClient.Client);
                while (!bDataRequested)
                {
                    if (bDataRequestedTimeout)
                    {
                        Debug.WriteLine("      > Sending  update firmware request cancel because of timeout!");
                        return;
                    }
                }

                DataTimer.Stop();
                bDataInfoSent = false;
                Debug.WriteLine("Sending firmware info...");
                Sender.vSendUpdateFirmwareInfo(_tcpClient.Client, firmware.Info);
                while (!bDataInfoSent)
                {
                    if (bDataRequestedTimeout)
                    {
                        Debug.WriteLine("     > Sending  firmware data info canceled because of timeout!");
                        return;
                    }
                }

                Debug.WriteLine("Sent  firmware data info");
                for (int i = 0; i < firmware.Pakages.Count; i++)
                {
                    Debug.WriteLine("Sending update data package " + i);
                    bFirmwarePackageSent = false;
                    Sender.vSendUpdateFirmwarePackage(_tcpClient.Client, firmware.Pakages[i]);
                    DataTimer.Stop();
                    while (!bFirmwarePackageSent)
                    {
                        if (bFirmwarePackageSentError_checksum)
                        {
                            if (bDataRequestedTimeout) return;
                            Sender.vSendUpdateFirmwarePackage(_tcpClient.Client, firmware.Pakages[i]);
                            bFirmwarePackageSentError_checksum = false;
                        }

                    }
                    //bwSimulator.ReportProgress(i + 3 / totalSteps * 100);
                }
            },
                TaskCreationOptions.LongRunning
            );
        }
        #endregion
        
        #region | Process received data |
        // tungpt added >>
        // variable for UPDATE_STATE firmware state
        bool bDataRequested = false;
        bool bDataRequestedTimeout = false;
        bool bDataInfoSent = false;
        bool bFirmwarePackageSent = false;
        bool bFirmwarePackageSentError_checksum = false;
        
        public event DataTimeoutHandler DataTimeout;
        public System.Timers.Timer DataTimer = new System.Timers.Timer(PACKAGE_TIMEOUT);
        // <<
        public TAG processData(byte[] btReceive)
        {
            if (Receiver.bAcceptCmdHeader(btReceive))
            {
                TAG result = TAG.SUCCESS;
                int index, packageLength;

                // tungpt added >>
                if (Receiver.bAcceptDataReqCmd(btReceive) || Receiver.bAcceptPlaylistReqCmd(btReceive) || Receiver.bAcceptFirmwareReqCmd(btReceive))
                {
                    Debug.WriteLine("     > Receive req update");
                    bDataRequested = true;
                    // start data timeout timer
                    bDataRequestedTimeout = false;
                    DataTimer.AutoReset = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();
                    
                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                }
                else if (Receiver.bAcceptDataInfo(btReceive) || Receiver.bAcceptPlaylistInfo(btReceive) || Receiver.bAcceptFirmwareInfo(btReceive))
                {
                    Debug.WriteLine("     > Receive req info");
                    bDataInfoSent = true;
                    // start data timeout timer
                    
                    bDataRequested = true;
                    // start data timeout timer
                    bDataRequestedTimeout = false;
                    DataTimer.AutoReset = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();
                    
                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                    
                }
                else if (Receiver.bAcceptDataPackage(btReceive) || Receiver.bAcceptFirmwarePackage(btReceive))
                {
                    Debug.WriteLine("     > Receive req package");
                    bFirmwarePackageSent = true;

                    bDataRequested = true;
                    // start data timeout timer
                    DataTimer.AutoReset = false;
                    bDataRequestedTimeout = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();
                    
                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                    
                }
                //else if (Receiver.bAcceptFirmwarePackageIDError(btReceive))
                //{
                //    Debug.WriteLine("     > Receive req package ID");
                //    bFirmwarePackageSent = true;

                //    bFirmwareRequested = true;
                //    // start data timeout timer
                //    DataTimer.AutoReset = false;
                //    bFirmwareRequestedTimeout = false;
                //    //DataTimer.Stop();
                //    //DataTimer.Start();

                //    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                //    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);

                //}
                    
                else if (Receiver.bAcceptDataPackageError(btReceive) || Receiver.bAcceptFirmwarePackageError(btReceive)
                    || Receiver.bAcceptDataPackageIDError(btReceive) || Receiver.bAcceptFirmwarePackageIDError(btReceive))
                {
                    bFirmwarePackageSentError_checksum = true;

                    bDataRequested = true;
                    // start data timeout timer
                    DataTimer.AutoReset = false;
                    bDataRequestedTimeout = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();
                    Debug.WriteLine("Send req error");
                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                    
                }
                    /*
                else if (Receiver.bAcceptPlaylistReqCmd(btReceive) || Receiver.bAcceptFirmwareReqCmd(btReceive))
                {
                    Debug.WriteLine("     > Receive req playlist update");
                    bDataRequested = true;
                    // start data timeout timer
                    bDataRequestedTimeout = false;
                    DataTimer.AutoReset = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();

                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                }
                else if (Receiver.bAcceptPlaylistInfo(btReceive) || Receiver.bAcceptFirmwareInfo(btReceive))
                {
                    Debug.WriteLine("     > Receive playlist info");
                    bDataInfoSent = true;
                    // start data timeout timer
                    
                    bDataRequested = true;
                    // start data timeout timer
                    bDataRequestedTimeout = false;
                    DataTimer.AutoReset = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();
                    
                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                    
                }
                else if (Receiver.bAcceptFirmwarePackage(btReceive))
                {
                    Debug.WriteLine("     > Receive req firmware package");
                    bDataRequested = true;
                    // start data timeout timer
                    bDataRequestedTimeout = false;
                    DataTimer.AutoReset = false;
                    //DataTimer.Stop();
                    //DataTimer.Start();

                    //Sender.vSendAckCmd(handler, DEFS.WMA_ACK);
                    //Sender.vSendUpdateFirmwareInfo(handler,parser.data.Length,parser.Offset);
                }*/
                else if (Receiver.bAcceptIMEI(btReceive))
                {
                    Debug.WriteLine("     > Same IMEI, disconnect old connection");
                    this.close();
                }
                else
                {
                    // TODO: Process byte data.
                    return TAG.SUCCESS;
                }
            }
            return TAG.SUCCESS;
        }
        #endregion
        
        #region | Simulator event |
        //public event ProgresChangedHandler ProgressChanged;
        public BackgroundWorker bwSimulator;
        ProgramData programToSent;
        int delay = 0;
        static Random ran = new Random();
        public void startProgramSim()
        {
            // in simulation mode, we send data after random time
            delay = (ran.Next(4)+1) * 1000;
            bwSimulator.RunWorkerAsync();
        }
        public void stopProgramSim()
        {
            if (bwSimulator.IsBusy)
            {

                // Notify the worker thread that a cancel has been requested.

                // The cancel will not actually happen until the thread in the

                // DoWork checks the m_oWorker.CancellationPending flag. 

                bwSimulator.CancelAsync();
            }
        }
        /// <summary>
        /// Time consuming operations go here </br>
        /// i.e. Database operations,Reporting
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void bwSimulatorDoWork(object sender, DoWorkEventArgs e)
        {
            // The sender is the BackgroundWorker object we need it to
            // report progress and check for cancellation.
            //NOTE : Never play with the UI thread here...
#if TEST_PROGRESS_UPDATE
            for (int i = 0; i < 100; i++)
            {

                Thread.Sleep(delay);
                // Periodically report progress to the main thread so that it can
                // update the UI.  In most cases you'll just need to send an
                // integer that will update a ProgressBar                    
                bwSimulator.ReportProgress(i);
                // Periodically check if a cancellation request is pending.
                // If the user clicks cancel the line
                // m_AsyncWorker.CancelAsync(); if ran above.  This
                // sets the CancellationPending to true.
                // You must check this flag in here and react to it.
                // We react to it by setting e.Cancel to true and leaving
                if (bwSimulator.CancellationPending)
                {
                    // Set the e.Cancel flag so that the WorkerCompleted event
                    // knows that the process was cancelled.
                    e.Cancel = true;
                    //bwSimulator.ReportProgress(0);
                    return;
                }
            }
            //Report 100% completion on operation completed
            bwSimulator.ReportProgress(100);
#else
            int totalSteps = 2 + programToSent.Pakages.Count;
            try
            {
                DataTimer.AutoReset = false;
                DataTimer.Stop();
                DataTimer.Start();
                Debug.WriteLine("Sending  update data request...");
                bDataRequested = false;
                Sender.vSendUpdateDataRequest(_tcpClient.Client, 1);
                while (!bDataRequested)
                {
                    if (bDataRequestedTimeout)
                    {
                        Debug.WriteLine("      > Sending  update data request cancel because of timeout!");
                        return;
                    }
                    if (bwSimulator.CancellationPending)
                    {
                        // Set the e.Cancel flag so that the WorkerCompleted event
                        // knows that the process was cancelled.
                        e.Cancel = true;
                        //bwSimulator.ReportProgress(0);
                        return;
                    }
                }
                // update progress bar
                bwSimulator.ReportProgress(1 / totalSteps * 100);

                DataTimer.Stop();
                bDataInfoSent = false;
                Debug.WriteLine("Sending  update data info...");
                Sender.vSendUpdateDataInfo(_tcpClient.Client, programToSent.Info);
                while (!bDataInfoSent)
                {
                    if (bDataRequestedTimeout)
                    {
                        Debug.WriteLine("     > Sending  update data info canceled because of timeout!");
                        return;
                    }
                    if (bwSimulator.CancellationPending)
                    {
                        // Set the e.Cancel flag so that the WorkerCompleted event
                        // knows that the process was canceled.
                        e.Cancel = true;
                        //bwSimulator.ReportProgress(0);
                        return;
                    }
                }
                bwSimulator.ReportProgress(2 / totalSteps * 100);
                Debug.WriteLine("Sent  update data info");
                if (bwSimulator.CancellationPending)
                {
                    // Set the e.Cancel flag so that the WorkerCompleted event
                    // knows that the process was canceled.
                    e.Cancel = true;
                    //bwSimulator.ReportProgress(0);
                    return;
                }
                for (int i = 0; i < programToSent.Pakages.Count; i++)
                {
                    Debug.WriteLine("Sending update data package " + i);
                    bFirmwarePackageSent = false;
                    Sender.vSendUpdateDataPackage(_tcpClient.Client, programToSent.Pakages[i]);
                    DataTimer.Stop();
                    while (!bFirmwarePackageSent)
                    {
                        if (bFirmwarePackageSentError_checksum)
                        {
                            if (bDataRequestedTimeout) return;
                            Sender.vSendUpdateDataPackage(_tcpClient.Client, programToSent.Pakages[i]);
                            bFirmwarePackageSentError_checksum = false;
                        }
                        if (bwSimulator.CancellationPending)
                        {
                            // Set the e.Cancel flag so that the WorkerCompleted event
                            // knows that the process was cancelled.
                            e.Cancel = true;
                            //bwSimulator.ReportProgress(0);
                            return;
                        }
                    }
                    bwSimulator.ReportProgress(i+3 / totalSteps * 100);
                }

                
            }
            catch (System.Exception ex)
            {
                Debug.WriteLine(ex.Message);
                //MessageBox.Show("Error while sending firmware to selected client\r\n" + ex.Message);
                return;
            }
#endif
        }

        void DataTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            // TODO: sent events to main form
            #if TEST_OFFLINE
            #else
                close();
            #endif
            bDataRequestedTimeout = true;
            DataTimeout(this, new EventArgs());
        }
        #endregion
    }
    /*
    public class ProgressChangedEventArgs : EventArgs
    {
        int value;

        public ProgressChangedEventArgs(int val):base()
        {
            value = val;
        }
    }
     * */
    public class StateObject
    {
        // Client  socket.
        //public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.

        public StringBuilder sb = new StringBuilder();

        public int index = -1;
    }
}
